package com.vsc.tomcat;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import org.apache.catalina.*;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.util.LifecycleSupport;
import org.apache.catalina.util.StringManager;
import org.apache.catalina.valves.ValveBase;

public class AccessLogValveSyslog extends ValveBase
	implements Lifecycle
{

	private String dateStamp;
	private String directory;
	protected static final String info = "org.apache.catalina.valves.AccessLogValve/1.0";
	protected LifecycleSupport lifecycle;
	protected static final String months[] = {
		"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", 
		"Nov", "Dec"
	};
	private String loghost;
	private boolean common;
	private boolean combined;
	private String pattern;
	private String prefix;
	private boolean rotatable;
	private StringManager sm;
	private boolean started;
	private String suffix;
	private PrintWriter writer;
	private SimpleDateFormat dateFormatter;
	private SimpleDateFormat dayFormatter;
	private SimpleDateFormat monthFormatter;
	private DecimalFormat timeTakenFormatter;
	private SimpleDateFormat yearFormatter;
	private SimpleDateFormat timeFormatter;
	private TimeZone timezone;
	private String timeZoneNoDST;
	private String timeZoneDST;
	private Date currentDate;
	private String space;
	private boolean resolveHosts;
	private long rotationLastChecked;
	private String condition;
	private String fileDateFormat;

	public AccessLogValveSyslog()
	{
		dateStamp = "";
		directory = "logs";
		lifecycle = new LifecycleSupport(this);
		loghost = "localhost";
		common = false;
		combined = false;
		pattern = null;
		prefix = "tomcat";
		rotatable = true;
		sm = StringManager.getManager("org.apache.catalina.valves");
		started = false;
		suffix = "";
		writer = null;
		dateFormatter = null;
		dayFormatter = null;
		monthFormatter = null;
		timeTakenFormatter = null;
		yearFormatter = null;
		timeFormatter = null;
		timezone = null;
		timeZoneNoDST = null;
		timeZoneDST = null;
		currentDate = null;
		space = " ";
		resolveHosts = false;
		rotationLastChecked = 0L;
		condition = null;
		fileDateFormat = null;
		setPattern("common");
	}

	public String getDirectory()
	{
		return directory;
	}

	public void setDirectory(String directory)
	{
		this.directory = directory;
	}

	public String getInfo()
	{
		return "org.apache.catalina.valves.AccessLogValve/1.0";
	}

	public String getPattern()
	{
		return pattern;
	}

	public void setPattern(String pattern)
	{
		if (pattern == null)
			pattern = "";
		if (pattern.equals("common"))
			pattern = "%h %l %u %t \"%r\" %s %b";
		if (pattern.equals("combined"))
			pattern = "%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\"";
		this.pattern = pattern;
		if (this.pattern.equals("%h %l %u %t \"%r\" %s %b"))
			common = true;
		else
			common = false;
		if (this.pattern.equals("%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\""))
			combined = true;
		else
			combined = false;
	}

	public String getPrefix()
	{
		return prefix;
	}

	public void setPrefix(String prefix)
	{
		this.prefix = prefix;
	}

	public boolean isRotatable()
	{
		return rotatable;
	}

	public void setRotatable(boolean rotatable)
	{
		this.rotatable = rotatable;
	}

	public String getSuffix()
	{
		return suffix;
	}

	public void setSuffix(String suffix)
	{
		this.suffix = suffix;
	}

	public void setResolveHosts(boolean resolveHosts)
	{
		this.resolveHosts = resolveHosts;
	}

	public boolean isResolveHosts()
	{
		return resolveHosts;
	}

	public String getCondition()
	{
		return condition;
	}

	public void setCondition(String condition)
	{
		this.condition = condition;
	}

	public String getFileDateFormat()
	{
		return fileDateFormat;
	}

	public void setFileDateFormat(String fileDateFormat)
	{
		this.fileDateFormat = fileDateFormat;
	}

	public void invoke(Request request, Response response)
		throws IOException, ServletException
	{
		long t1 = System.currentTimeMillis();
		getNext().invoke(request, response);
		long t2 = System.currentTimeMillis();
		long time = t2 - t1;
		if (condition != null && request.getRequest().getAttribute(condition) != null)
			return;
		Date date = getDate();
		StringBuffer result = new StringBuffer();
		if (common || combined)
		{
			String value = null;
			if (isResolveHosts())
				result.append(request.getRemoteHost());
			else
				result.append(request.getRemoteAddr());
			result.append(" - ");
			value = request.getRemoteUser();
			if (value == null)
			{
				result.append("- ");
			} else
			{
				result.append(value);
				result.append(space);
			}
			result.append("[");
			result.append(dayFormatter.format(date));
			result.append('/');
			result.append(lookup(monthFormatter.format(date)));
			result.append('/');
			result.append(yearFormatter.format(date));
			result.append(':');
			result.append(timeFormatter.format(date));
			result.append(space);
			result.append(getTimeZone(date));
			result.append("] \"");
			result.append(request.getMethod());
			result.append(space);
			result.append(request.getRequestURI());
			if (request.getQueryString() != null)
			{
				result.append('?');
				result.append(request.getQueryString());
			}
			result.append(space);
			result.append(request.getProtocol());
			result.append("\" ");
			result.append(response.getStatus());
			result.append(space);
			int length = response.getContentCount();
			if (length <= 0)
				value = "-";
			else
				value = (new StringBuilder()).append(length).toString();
			result.append(value);
			if (combined)
			{
				result.append(space);
				result.append("\"");
				String referer = request.getHeader("referer");
				if (referer != null)
					result.append(referer);
				else
					result.append("-");
				result.append("\"");
				result.append(space);
				result.append("\"");
				String ua = request.getHeader("user-agent");
				if (ua != null)
					result.append(ua);
				else
					result.append("-");
				result.append("\"");
			}
		} else
		{
			boolean replace = false;
			for (int i = 0; i < pattern.length(); i++)
			{
				char ch = pattern.charAt(i);
				if (replace)
				{
					if ('{' == ch)
					{
						StringBuffer name = new StringBuffer();
						int j;
						for (j = i + 1; j < pattern.length() && '}' != pattern.charAt(j); j++)
							name.append(pattern.charAt(j));

						if (j + 1 < pattern.length())
						{
							j++;
							result.append(replace(name.toString(), pattern.charAt(j), request, response));
							i = j;
						} else
						{
							result.append(replace(ch, date, request, response, time));
						}
					} else
					{
						result.append(replace(ch, date, request, response, time));
					}
					replace = false;
				} else
				if (ch == '%')
					replace = true;
				else
					result.append(ch);
			}

		}
		log(result.toString(), date);
	}

	private synchronized void close()
	{
		if (writer == null)
		{
			return;
		} else
		{
			writer.flush();
			writer.close();
			writer = null;
			dateStamp = "";
			return;
		}
	}

	public void log(String message, Date date)
	{
		try {
			Runtime.getRuntime().exec("logger -p " + prefix + " " + message);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private String lookup(String month)
	{
		int index;
		try
		{
			index = Integer.parseInt(month) - 1;
		}
		catch (Throwable t)
		{
			index = 0;
		}
		return months[index];
	}

	private String replace(char pattern, Date date, Request request, Response response, long time)
	{
		String value = null;
		if (pattern == 'a')
			value = request.getRemoteAddr();
		else
		if (pattern == 'A')
			try
			{
				value = InetAddress.getLocalHost().getHostAddress();
			}
			catch (Throwable e)
			{
				value = "127.0.0.1";
			}
		else
		if (pattern == 'b')
		{
			int length = response.getContentCount();
			if (length <= 0)
				value = "-";
			else
				value = (new StringBuilder()).append(length).toString();
		} else
		if (pattern == 'B')
			value = (new StringBuilder()).append(response.getContentLength()).toString();
		else
		if (pattern == 'h')
			value = request.getRemoteHost();
		else
		if (pattern == 'H')
			value = request.getProtocol();
		else
		if (pattern == 'l')
			value = "-";
		else
		if (pattern == 'm')
		{
			if (request != null)
				value = request.getMethod();
			else
				value = "";
		} else
		if (pattern == 'p')
			value = (new StringBuilder()).append(request.getServerPort()).toString();
		else
		if (pattern == 'D')
			value = (new StringBuilder()).append(time).toString();
		else
		if (pattern == 'q')
		{
			String query = null;
			if (request != null)
				query = request.getQueryString();
			if (query != null)
				value = (new StringBuilder("?")).append(query).toString();
			else
				value = "";
		} else
		if (pattern == 'r')
		{
			StringBuffer sb = new StringBuffer();
			if (request != null)
			{
				sb.append(request.getMethod());
				sb.append(space);
				sb.append(request.getRequestURI());
				if (request.getQueryString() != null)
				{
					sb.append('?');
					sb.append(request.getQueryString());
				}
				sb.append(space);
				sb.append(request.getProtocol());
			} else
			{
				sb.append("- - ");
				sb.append(request.getProtocol());
			}
			value = sb.toString();
		} else
		if (pattern == 'S')
		{
			if (request != null)
			{
				if (request.getSession(false) != null)
					value = request.getSessionInternal(false).getIdInternal();
				else
					value = "-";
			} else
			{
				value = "-";
			}
		} else
		if (pattern == 's')
		{
			if (response != null)
				value = (new StringBuilder()).append(response.getStatus()).toString();
			else
				value = "-";
		} else
		if (pattern == 't')
		{
			StringBuffer temp = new StringBuffer("[");
			temp.append(dayFormatter.format(date));
			temp.append('/');
			temp.append(lookup(monthFormatter.format(date)));
			temp.append('/');
			temp.append(yearFormatter.format(date));
			temp.append(':');
			temp.append(timeFormatter.format(date));
			temp.append(' ');
			temp.append(getTimeZone(date));
			temp.append(']');
			value = temp.toString();
		} else
		if (pattern == 'T')
			value = timeTakenFormatter.format((double)time / 1000D);
		else
		if (pattern == 'u')
		{
			if (request != null)
				value = request.getRemoteUser();
			if (value == null)
				value = "-";
		} else
		if (pattern == 'U')
		{
			if (request != null)
				value = request.getRequestURI();
			else
				value = "-";
		} else
		if (pattern == 'v')
			value = request.getServerName();
		else
			value = (new StringBuilder("???")).append(pattern).append("???").toString();
		if (value == null)
			return "";
		else
			return value;
	}

	private String replace(String header, char type, Request request, Response response)
	{
		Object value;
		value = null;
		switch (type)
		{
		case 105: // 'i'
			if (request != null)
				value = request.getHeader(header);
			else
				value = "??";
			break;

		case 99: // 'c'
			Cookie c[] = request.getCookies();
			for (int i = 0; c != null && i < c.length; i++)
			{
				if (!header.equals(c[i].getName()))
					continue;
				value = c[i].getValue();
				break;
			}

			break;

		case 114: // 'r'
			if (request != null)
				value = request.getAttribute(header);
			else
				value = "??";
			break;

		case 115: // 's'
			if (request != null)
			{
				HttpSession sess = request.getSession(false);
				if (sess != null)
					value = sess.getAttribute(header);
			}
			break;

		default:
			value = "???";
			break;
		}
		if (value instanceof String)
			return (String)value;
		return "-";
	}

	private Date getDate()
	{
		if (currentDate == null)
		{
			currentDate = new Date();
		} else
		{
			long systime = System.currentTimeMillis();
			if (systime - currentDate.getTime() > 1000L)
				currentDate = new Date(systime);
		}
		return currentDate;
	}

	private String getTimeZone(Date date)
	{
		if (timezone.inDaylightTime(date))
			return timeZoneDST;
		else
			return timeZoneNoDST;
	}

	private String calculateTimeZoneOffset(long offset)
	{
		StringBuffer tz = new StringBuffer();
		if (offset < 0L)
		{
			tz.append("-");
			offset = -offset;
		} else
		{
			tz.append("+");
		}
		long hourOffset = offset / 0x36ee80L;
		long minuteOffset = (offset / 60000L) % 60L;
		if (hourOffset < 10L)
			tz.append("0");
		tz.append(hourOffset);
		if (minuteOffset < 10L)
			tz.append("0");
		tz.append(minuteOffset);
		return tz.toString();
	}

	public void addLifecycleListener(LifecycleListener listener)
	{
		lifecycle.addLifecycleListener(listener);
	}

	public LifecycleListener[] findLifecycleListeners()
	{
		return lifecycle.findLifecycleListeners();
	}

	public void removeLifecycleListener(LifecycleListener listener)
	{
		lifecycle.removeLifecycleListener(listener);
	}

	public void start()
		throws LifecycleException
	{
		if (started)
			throw new LifecycleException(sm.getString("accessLogValve.alreadyStarted"));
		lifecycle.fireLifecycleEvent("start", null);
		started = true;
		timezone = TimeZone.getDefault();
		timeZoneNoDST = calculateTimeZoneOffset(timezone.getRawOffset());
		Calendar calendar = Calendar.getInstance(timezone);
		int offset = calendar.get(16);
		timeZoneDST = calculateTimeZoneOffset(timezone.getRawOffset() + offset);
		if (fileDateFormat == null || fileDateFormat.length() == 0)
			fileDateFormat = "yyyy-MM-dd";
		dateFormatter = new SimpleDateFormat(fileDateFormat);
		dateFormatter.setTimeZone(timezone);
		dayFormatter = new SimpleDateFormat("dd");
		dayFormatter.setTimeZone(timezone);
		monthFormatter = new SimpleDateFormat("MM");
		monthFormatter.setTimeZone(timezone);
		yearFormatter = new SimpleDateFormat("yyyy");
		yearFormatter.setTimeZone(timezone);
		timeFormatter = new SimpleDateFormat("HH:mm:ss");
		timeFormatter.setTimeZone(timezone);
		currentDate = new Date();
		dateStamp = dateFormatter.format(currentDate);
		timeTakenFormatter = new DecimalFormat("0.000");
	}

	public void stop()
		throws LifecycleException
	{
		if (!started)
		{
			throw new LifecycleException(sm.getString("accessLogValve.notStarted"));
		} else
		{
			lifecycle.fireLifecycleEvent("stop", null);
			started = false;
			close();
			return;
		}
	}

	public String getLoghost()
	{
		return loghost;
	}

	public void setLoghost(String loghost)
	{
		this.loghost = loghost;
	}

}
